using System;
using System.Collections.Generic;
using UnityEngine;
using DanielSig;
using DanielSig.UnityExtensions;

namespace DanielSig.Geometry
{
	public struct Carving
	{
		private const float _MAX_DIST = 50F;
		private const float _DISTANCE_FLOAT_TO_SHORT = short.MaxValue / _MAX_DIST;
		private const float _DISTANCE_SHORT_TO_FLOAT = 1F / _DISTANCE_FLOAT_TO_SHORT;
		public const int CARVING_NUM_BYTES = 6;
		
		public UnitVector32 Normal;
		private Int16 _distance;
		
		public void Mirror()
		{
			Normal.vector = -Normal.vector;
			_distance = (short)-_distance;
		}
		
		public Carving Mirrored
		{
			get
			{
				return new Carving(-Normal.vector, -Distance);
			}
		}
		public float Distance
		{
			get
			{
				return _distance * _DISTANCE_SHORT_TO_FLOAT;
			}
			set
			{
				_distance = (short)Mathf.Clamp((int)(value * _DISTANCE_FLOAT_TO_SHORT), (int)short.MinValue, (int)short.MaxValue);
			}
		}
		public Vector3 NormalVector
		{
			get
			{
				return Normal.vector;
			}
			set
			{
				Normal.vector = value;
			}
		}
		public byte[] RawData
		{
			get
			{
				uint bitIndex = 0;
				byte[] data = new byte[CARVING_NUM_BYTES];
				data.WriteUInt(Normal.rawData, ref bitIndex);
				data.WriteShort(_distance, ref bitIndex);
				return data;
			}
			set
			{
				uint bitIndex = 0;
				if(value.Length != CARVING_NUM_BYTES) throw new ArgumentException("RawData must be exactly 6 bytes long");
				Normal.rawData = value.ReadUint(ref bitIndex);
				_distance = value.ReadShort(ref bitIndex);
			}
		}
		
		public Carving(Vector3 normal, float distance)
		{
			NormalVector = normal;
			Distance = distance;
		}
		public Carving(byte[] rawData)
		{
			RawData = rawData;
		}
		
		public static explicit operator Plane(Carving carv)
		{
			return new Plane(carv.NormalVector, carv.Distance);
		}
		public static explicit operator Carving(Plane plane)
		{
			return new Carving(plane.normal, plane.distance);
		}
		
		public bool Symmetrical(Carving other, float threshold)
		{
			return (NormalVector.Aproximates(other.NormalVector, threshold) && Distance.Aproximates(other.Distance, threshold)) || (NormalVector.Aproximates(-other.NormalVector, threshold) && Distance.Aproximates(-other.Distance, threshold));
		}
		public bool Symmetrical(Carving other)
		{
			return (Normal == other.Normal && _distance == other._distance) || (NormalVector == -other.NormalVector && _distance == -other._distance);
		}
		public bool Parallel(Carving other, float threshold)
		{
			return NormalVector.Aproximates(other.NormalVector, threshold) || NormalVector.Aproximates(-other.NormalVector, threshold);
		}
		public bool Parallel(Carving other)
		{
			return (Normal == other.Normal) || (NormalVector == -other.NormalVector);
		}
		public void RemovePoints<T>(LinkedList<T> points, Func<T, Vector3> getPoint)
		{
			if(points.Count <= 0) return;
			
			LinkedListNode<T> link = points.First;
			while(link != null)
			{
				LinkedListNode<T> next = link.Next;
				
				Vector3 point = getPoint(link.Value);
				
				if(point > this)
				{
					points.Remove(link);
				}
				
				link = next;
			}
		}
		
		public static float operator -(Carving carving, Vector3 point)
		{
			return ((Plane)carving).GetDistanceToPoint(point);
		}
		public static float operator -(Vector3 point, Carving carving)
		{
			return -(carving - point);
		}
		
		public static bool operator >(Vector3 point, Carving carving)
		{
			return ((Plane)carving).GetSide(point);
		}
		public static bool operator >(Carving carving, Vector3 point)
		{
			return !(point > carving);
		}
		public static bool operator <(Vector3 point, Carving carving)
		{
			return !(point > carving);
		}
		public static bool operator <(Carving carving, Vector3 point)
		{
			return point > carving;
		}
		
		public static Carving operator ~(Carving carving)
		{
			return carving.Mirrored;
		}
		
		public static float operator *(Carving carving, Carving other)
		{
			return ((carving.NormalVector.Dot(other.NormalVector) * 0.5F) + 0.5F) / ((carving.Distance - other.Distance).Abs() + 1F);
		}
		public static float operator *(Carving carving, Vector3 point)
		{
			return carving - point;
		}
		public static float operator *(Vector3 point, Carving carving)
		{
			return point - carving;
		}
		public static Carving operator *(Matrix4x4 matrix, Carving carving)
		{
			Vector3 normal = matrix.MultiplyVector(carving.NormalVector);
			float d = normal.Dot(matrix.MultiplyPoint3x4(carving.NormalVector * carving.Distance));
			return new Carving(normal, d);
		}
		
		public static bool operator >(Carving left, Carving right)
		{
			return left.Normal.rawData == right.Normal.rawData && left._distance > right._distance;
		}
		public static bool operator <(Carving left, Carving right)
		{
			return left.Normal.rawData == right.Normal.rawData && left._distance < right._distance;
		}
		
		public static bool operator ==(Carving left, Carving right)
		{
			return left._distance == right._distance && left.Normal.rawData == right.Normal.rawData;
		}
		public static bool operator !=(Carving left, Carving right)
		{
			return left._distance != right._distance || left.Normal.rawData != right.Normal.rawData;
		}
		public override bool Equals(object other)
		{
			if(other is Carving) return this == (Carving)other;
			else if(other is Plane) return this == (Carving)other;
			else return false;
		}
		public override int GetHashCode()
		{
			return Normal.GetHashCode() ^ (int)_distance;
		}
	}
}

